package cn.com.stary1993.kite.captcha.support.behavior;

import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.exception.ImageCaptchaException;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.ApiResponseStatusConstant;
import cloud.tianai.captcha.generator.ImageCaptchaGenerator;
import cloud.tianai.captcha.generator.common.model.dto.GenerateParam;
import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo;
import cloud.tianai.captcha.resource.ImageCaptchaResourceManager;
import cloud.tianai.captcha.validator.ImageCaptchaValidator;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator;
import cn.com.stary1993.kite.captcha.config.BehaviorImageCaptchaProperties;
import cn.com.stary1993.kite.captcha.store.CacheStore;
import cn.com.stary1993.kite.captcha.support.CaptchaValidException;
import cn.com.stary1993.kite.captcha.vo.BehaviorCaptchaVO;
import cn.com.stary1993.kite.captcha.vo.CaptchaResponse;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.*;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 默认的 行为图片验证码
 *
 * @author stary1993
 */
@Slf4j
@Data
public class DefaultBehaviorImageCaptcha extends BehaviorImageCaptcha {

    /**
     * 图片验证码生成器.
     */
    private ImageCaptchaGenerator template;

    /**
     * 图片验证码校验器.
     */
    private ImageCaptchaValidator imageCaptchaValidator;

    /**
     * 缓冲存储.
     */
    private CacheStore cacheStore;

    /**
     * 验证码配置属性.
     */
    private BehaviorImageCaptchaProperties prop;

    /**
     * 默认的过期时间.
     */
    private long defaultExpire = 20000L;

    public DefaultBehaviorImageCaptcha(ImageCaptchaGenerator template, ImageCaptchaValidator imageCaptchaValidator,
                                       CacheStore cacheStore, BehaviorImageCaptchaProperties prop) {
        this.prop = prop;
        setImageCaptchaTemplate(template);
        setImageCaptchaValidator(imageCaptchaValidator);
        setCacheStore(cacheStore);
        // 默认过期时间
        Long defaultExpire = prop.getExpire().getDefaultExpire();
        if (defaultExpire != null && defaultExpire > 0) {
            this.defaultExpire = defaultExpire;
        }
    }

    @Override
    public CaptchaResponse<BehaviorCaptchaVO> generate() {
        // 生成滑块验证码
        return generate(CaptchaTypeConstant.SLIDER);
    }

    @Override
    public CaptchaResponse<BehaviorCaptchaVO> generate(String type) {
        return generate(type, false);
    }

    @Override
    public CaptchaResponse<BehaviorCaptchaVO> generate(String type, boolean obfuscate) {
        return generate(type, obfuscate, BehaviorImageCaptchaFormat.JPEG_PNG);
    }

    @Override
    public CaptchaResponse<BehaviorCaptchaVO> generate(String type,
                                                       boolean obfuscate,
                                                       BehaviorImageCaptchaFormat captchaImageType) {
        GenerateParam param = new GenerateParam();
        if (BehaviorImageCaptchaFormat.WEBP.equals(captchaImageType)) {
            param.setBackgroundFormatName("webp");
            param.setTemplateFormatName("webp");
        } else {
            param.setBackgroundFormatName("jpeg");
            param.setTemplateFormatName("png");
        }
        param.setObfuscate(obfuscate);
        param.setType(type);
        return generate(param);
    }

    @Override
    public CaptchaResponse<BehaviorCaptchaVO> generate(GenerateParam param) {
        ImageCaptchaInfo slideImageInfo = getImageCaptchaTemplate().generateCaptchaImage(param);
        // 重试一次
        if (slideImageInfo == null) {
            slideImageInfo = getImageCaptchaTemplate().generateCaptchaImage(param);
        }
        return afterGenerateSliderCaptcha(slideImageInfo);
    }


    private CaptchaResponse<BehaviorCaptchaVO> afterGenerateSliderCaptcha(ImageCaptchaInfo slideImageInfo) {
        if (slideImageInfo == null) {
            // 要是生成失败
            throw new ImageCaptchaException("生成滑块验证码失败，验证码生成为空");
        }
        // 生成ID
        String id = generatorId();
        // 生成校验数据
        Map<String, Object> validData = getImageCaptchaValidator().generateImageCaptchaValidData(slideImageInfo);
        // 存到缓存里
        cacheVerification(id, slideImageInfo.getType(), validData);
        BehaviorCaptchaVO verificationVO = new BehaviorCaptchaVO();
        verificationVO.setType(slideImageInfo.getType());
        verificationVO.setBackgroundImage(slideImageInfo.getBackgroundImage());
        verificationVO.setTemplateImage(slideImageInfo.getTemplateImage());
        verificationVO.setBackgroundImageTag(slideImageInfo.getBackgroundImageTag());
        verificationVO.setTemplateImageTag(slideImageInfo.getTemplateImageTag());
        verificationVO.setBackgroundImageWidth(slideImageInfo.getBackgroundImageWidth());
        verificationVO.setBackgroundImageHeight(slideImageInfo.getBackgroundImageHeight());
        verificationVO.setTemplateImageWidth(slideImageInfo.getTemplateImageWidth());
        verificationVO.setTemplateImageHeight(slideImageInfo.getTemplateImageHeight());
        verificationVO.setData(slideImageInfo.getData() == null ? null : slideImageInfo.getData().getViewData());

        List<BehaviorImageCaptchaProperties.Resource> boxBackgroundImageList = prop.getBoxBackgroundImageList();
        if (CollUtil.isNotEmpty(boxBackgroundImageList)) {
            verificationVO.setBoxBackgroundImage(resourceToBase64(RandomUtil.randomEle(boxBackgroundImageList)));
        }
        List<BehaviorImageCaptchaProperties.Resource> sliderButtonImageList = prop.getSliderButtonImageList();
        if (CollUtil.isNotEmpty(sliderButtonImageList)) {
            verificationVO.setSliderButtonImage(resourceToBase64(RandomUtil.randomEle(sliderButtonImageList)));
        }
        List<BehaviorImageCaptchaProperties.Resource> logoImageList = prop.getLogoImageList();
        if (CollUtil.isNotEmpty(logoImageList)) {
            verificationVO.setLogoImage(resourceToBase64(RandomUtil.randomEle(logoImageList)));
        }
        List<BehaviorImageCaptchaProperties.Resource> loadingImageList = prop.getLoadingImageList();
        if (CollUtil.isNotEmpty(loadingImageList)) {
            verificationVO.setLoadingImage(resourceToBase64(RandomUtil.randomEle(loadingImageList)));
        }

        return CaptchaResponse.of(id, verificationVO);
    }

    /**
     * 图片资源转成base64
     *
     * @param imageResource
     * @return
     */
    @SneakyThrows
    private String resourceToBase64(BehaviorImageCaptchaProperties.Resource imageResource) {
        BehaviorImageCaptchaProperties.ResourceType resourceType = imageResource.getType();
        String path = imageResource.getPath();
        InputStream resourceStream = null;

        if (ObjectUtil.equals(resourceType, BehaviorImageCaptchaProperties.ResourceType.CLASSPATH)) {
            resourceStream = ClassUtil.getClassLoader().getResourceAsStream(path);

        } else if (ObjectUtil.equals(resourceType, BehaviorImageCaptchaProperties.ResourceType.URL)) {
            resourceStream = URLUtil.getStream(URLUtil.url(path));
        }
        if (resourceStream != null) {
            return URLUtil.getDataUriBase64("image/jpg", Base64.getEncoder().encodeToString(IoUtil.readBytes(resourceStream)));
        }
        return null;
    }


    @Override
    public ApiResponse<?> matching(String id, ImageCaptchaTrack imageCaptchaTrack) {
        Map<String, Object> cachePercentage = getVerification(id);
        if (cachePercentage == null) {
            return ApiResponse.ofMessage(ApiResponseStatusConstant.EXPIRED);
        }
        return getImageCaptchaValidator().valid(imageCaptchaTrack, cachePercentage);
    }

    /**
     * 兼容一下旧版本，新版本建议使用 {@link BehaviorImageCaptcha#matching(String, ImageCaptchaTrack)}
     *
     * @param id         id
     * @param percentage 百分比数据
     * @return boolean
     */
    @Override
    public boolean matching(String id, Float percentage) {
        Map<String, Object> cachePercentage = getVerification(id);
        if (cachePercentage == null) {
            return false;
        }
        ImageCaptchaValidator imageCaptchaValidator = getImageCaptchaValidator();
        if (!(imageCaptchaValidator instanceof SimpleImageCaptchaValidator)) {
            return false;
        }
        SimpleImageCaptchaValidator simpleImageCaptchaValidator = (SimpleImageCaptchaValidator) imageCaptchaValidator;
        Float oriPercentage = simpleImageCaptchaValidator.getFloatParam(SimpleImageCaptchaValidator.PERCENTAGE_KEY,
                cachePercentage);
        // 读容错值
        Float tolerant = simpleImageCaptchaValidator.getFloatParam(SimpleImageCaptchaValidator.TOLERANT_KEY,
                cachePercentage, simpleImageCaptchaValidator.getDefaultTolerant());
        return simpleImageCaptchaValidator.checkPercentage(percentage, oriPercentage, tolerant);
    }

    protected String generatorId() {
        return IdUtil.fastUUID();
    }

    /**
     * 通过缓存获取百分比
     *
     * @param id 验证码ID
     * @return Map<String, Object>
     */
    protected Map<String, Object> getVerification(String id) {
        return (Map<String, Object>) getCacheStore().getAndRemoveCache(getKey(id));
    }

    /**
     * 缓存验证码
     *
     * @param id
     * @param type
     * @param validData
     */
    protected void cacheVerification(String id, String type, Map<String, Object> validData) {
        List<Map<BehaviorImageCaptchaProperties.TemplateModel, Long>> modelExpire = prop.getExpire().getModelExpire();
        Long expire = defaultExpire;
        if (CollUtil.isNotEmpty(modelExpire)) {
            expire = modelExpire.stream()
                    .map(item -> item.getOrDefault(BehaviorImageCaptchaProperties.TemplateModel.valueOf(type), defaultExpire)).findFirst().get();
        }
        if (!getCacheStore().setCache(getKey(id), validData, expire, TimeUnit.MILLISECONDS)) {
            log.error("缓存验证码数据失败,id={}, validData={}", id, validData);
            throw new CaptchaValidException(type, "缓存验证码数据失败");
        }
    }

    protected String getKey(String id) {
        return prop.getKeyPrefix().concat(":").concat(id);
    }

    @Override
    public ImageCaptchaResourceManager getImageCaptchaResourceManager() {
        return getImageCaptchaTemplate().getImageResourceManager();
    }

    @Override
    public ImageCaptchaValidator getImageCaptchaValidator() {
        return this.imageCaptchaValidator;
    }

    @Override
    public void setImageCaptchaValidator(ImageCaptchaValidator imageCaptchaValidator) {
        this.imageCaptchaValidator = imageCaptchaValidator;
    }

    @Override
    public ImageCaptchaGenerator getImageCaptchaTemplate() {
        return this.template;
    }

    @Override
    public void setImageCaptchaTemplate(ImageCaptchaGenerator imageCaptchaGenerator) {
        this.template = imageCaptchaGenerator;
    }

    @Override
    public CacheStore getCacheStore() {
        return this.cacheStore;
    }

    @Override
    public void setCacheStore(CacheStore cacheStore) {
        this.cacheStore = cacheStore;
    }

}
